// $Id: interface_table.hpp 2 2009-09-02 08:47:42Z joseph2002 $
/*
 * Copyright 2009 Joseph Wu
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
 *
 * For more information, see http://xpcomplus.sourceforge.net
 */

#ifndef XPCOM_INTERFACE_TABLE_HPP
#define XPCOM_INTERFACE_TABLE_HPP

#include <xpcom/config.hpp>
#include <boost/mpl/at.hpp>
#include <boost/mpl/back_inserter.hpp>
#include <boost/mpl/copy.hpp>
#include <boost/mpl/empty.hpp>
#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/front.hpp>
#include <boost/mpl/has_key.hpp>
#include <boost/mpl/insert.hpp>
#include <boost/mpl/inserter.hpp>
#include <boost/mpl/map.hpp>
#include <boost/mpl/pair.hpp>
#include <boost/mpl/push_back.hpp>
#include <boost/mpl/size.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/void.hpp>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/iteration/local.hpp>
#include <boost/static_assert.hpp>
#include <boost/type_traits/is_convertible.hpp>
#include <xpcom/types.hpp>
#include <xpcom/type_info.hpp>

#ifdef BOOST_HAS_ABI_HEADERS
#include BOOST_ABI_PREFIX
#endif

namespace xpcom {

//
// Interface table entry structure.
//
// This structure is compatible with mozilla's QITableEntry.
// And mozilla has even a special IID for query_interface to return pointer to
// the interface table entries. So that we give it a type_info specialization.
//
struct interface_table_entry
{
    const uuid* iid; // null indicates end of the array
    offset32_t  offset;
};

XPCOM_TYPE_INFO_DECL(interface_table_entry, boost::mpl::void_)
XPCOM_TYPE_INFO_ID(interface_table_entry,
    { 0x23E017CC, 0x5D6F, 0x430c,
    { 0xB3, 0xE6, 0x9D, 0x32, 0x65, 0x70, 0xD6, 0xB8 } })

//
// General query_interface implementation using interface offset table.
//
XPCOM_DECL result_t XPCOM_FASTCALL
interface_table_query(
        void* pthis, const interface_table_entry* entries,
        const uuid& iid, void** obj) throw();

namespace detail {

// Interface table utilities
template <typename T>
struct is_itable_interface :
    boost::is_convertible<T*, interface*>
{
};

template <typename Entries, int N>
struct itable_at :
    boost::mpl::eval_if_c<
        (N >= 0 && N < boost::mpl::size<Entries>::value),
        boost::mpl::at_c<Entries, N>,
        boost::mpl::void_
    >
{
};

template <typename Entry>
XPCOM_FORCEINLINE
const uuid* itable_iid()
{
    return &type_info<typename boost::mpl::first<Entry>::type>::id();
}

template <>
XPCOM_FORCEINLINE
const uuid* itable_iid<boost::mpl::void_>()
{
    return 0;
}

template <typename T, typename Entry>
struct itable_offset
{
    typedef typename boost::mpl::first<Entry>::type interface_type;
    typedef typename boost::mpl::second<Entry>::type impl_type;

    XPCOM_FORCEINLINE
    static offset32_t value()
    {
        return static_cast<offset32_t>(
            reinterpret_cast<char*>(static_cast<interface_type*>(
                static_cast<impl_type*>((T*)0x1000))) -
            reinterpret_cast<char*>((T*)0x1000));
    }
};

template <typename T>
struct itable_offset<T, boost::mpl::void_>
{
    XPCOM_FORCEINLINE
    static offset32_t value()
    {
        return 0;
    }
};

// Interface table construction
template <typename Entries, typename Entry>
struct itable_insert_entry :
    boost::mpl::eval_if<
        boost::mpl::has_key<Entries, typename boost::mpl::first<Entry>::type>,
        boost::mpl::identity<Entries>,
        boost::mpl::insert<Entries, Entry>
    >
{
};

template <typename Entries, typename T, typename Impl = T>
struct itable_add_entry
{
    BOOST_STATIC_ASSERT(is_itable_interface<Impl>::value);

    typedef boost::mpl::pair<typename type_info<T>::type, Impl> entry;

    typedef typename itable_add_entry<
        typename itable_insert_entry<Entries, entry>::type,
        typename type_info<T>::base_type,
        Impl
    >::type type;
};

template <typename Entries, typename Impl>
struct itable_add_entry<Entries, interface, Impl>
{
    typedef Entries type; // do not add plain interface entry
};

template <typename Entries, typename Impl>
struct itable_add_entry<Entries, boost::mpl::void_, Impl>
{
    typedef Entries type;
};

template <typename Seq>
struct itable_entries
{
    BOOST_STATIC_ASSERT(!boost::mpl::empty<Seq>::value);

    // Insert into a map first to avoid duplicated interface
    typedef typename boost::mpl::copy<
        Seq,
        boost::mpl::inserter<
            boost::mpl::map<>,
            itable_add_entry<boost::mpl::_1, boost::mpl::_2>
        >
    >::type type1;

    // Copy to vector for indexed access
    typedef typename boost::mpl::copy<
        type1,
        boost::mpl::back_inserter<boost::mpl::vector<> >
    >::type type2;

    // Append <interface, Seq<0> >
    typedef typename boost::mpl::push_back<
        type2,
        boost::mpl::pair<interface, typename boost::mpl::front<Seq>::type>
    >::type type;
};

// Interface table that holds up to 5, 10, 20 interfaces.
#define XPCOM_itable_entry(n) \
    { \
        itable_iid<typename itable_at<Entries, n>::type>(), \
        itable_offset<T, typename itable_at<Entries, n>::type>::value() \
    },

// 5
template <typename T, typename Entries>
struct itable5
{
    typedef itable5 type;
    typedef Entries entries_type;
    static const interface_table_entry entries[];
};

template <typename T, typename Entries>
const interface_table_entry itable5<T, Entries>::entries[] =
{
#define BOOST_PP_LOCAL_LIMITS (0, 4)
#define BOOST_PP_LOCAL_MACRO(n) XPCOM_itable_entry(n)
#include BOOST_PP_LOCAL_ITERATE()
    { 0, 0 }
};

// 10
template <typename T, typename Entries>
struct itable10
{
    typedef itable10 type;
    typedef Entries entries_type;
    static const interface_table_entry entries[];
};

template <typename T, typename Entries>
const interface_table_entry itable10<T, Entries>::entries[] =
{
#define BOOST_PP_LOCAL_LIMITS (0, 9)
#define BOOST_PP_LOCAL_MACRO(n) XPCOM_itable_entry(n)
#include BOOST_PP_LOCAL_ITERATE()
    { 0, 0 }
};

// 20
template <typename T, typename Entries>
struct itable20
{
    typedef itable20 type;
    typedef Entries entries_type;
    static const interface_table_entry entries[];
};

template <typename T, typename Entries>
const interface_table_entry itable20<T, Entries>::entries[] =
{
#define BOOST_PP_LOCAL_LIMITS (0, 19)
#define BOOST_PP_LOCAL_MACRO(n) XPCOM_itable_entry(n)
#include BOOST_PP_LOCAL_ITERATE()
    { 0, 0 }
};

#undef XPCOM_itable_entry

// Select proper interface table based on size of the input sequence.
template <typename T, typename Seq>
struct itable_select
{
    typedef typename itable_entries<Seq>::type entries;

    typedef typename boost::mpl::eval_if_c<
        (boost::mpl::size<entries>::value <= 5),
        itable5<T, entries>,
        boost::mpl::eval_if_c<
            (boost::mpl::size<entries>::value <= 10),
            itable10<T, entries>,
            itable20<T, entries>
        >
    >::type type;
};

} // namespace xpcom::detail

template <typename T, typename Seq>
struct interface_table :
    detail::itable_select<T, Seq>::type
{
};

} // namespace xpcom

#ifdef BOOST_HAS_ABI_HEADERS
#include BOOST_ABI_SUFFIX
#endif

#endif // XPCOM_INTERFACE_TABLE_HPP
